package edu.unc.ils.util;

import java.io.BufferedReader;

import java.io.FileReader;
import java.util.LinkedHashSet;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import edu.unc.ils.mrc.vocabulary.Vocabulary;
import edu.unc.ils.mrc.vocabulary.VocabularyGraph;
import edu.unc.ils.mrc.vocabulary.VocabularyH2;
import Jama.Matrix;

public class IndexerConsistency 
{
    public static void main(String[] args) throws Exception 
    {
        String idFile = "/Users/cwillis/dev/mrc/dryad_hive/data/input/taxa/dryad_ids.txt";
        String biosisFile = "/Users/cwillis/dev/mrc/dryad_hive/data/input/taxa/biosis_taxa_final_names.txt";
        String dryadFile = "/Users/cwillis/dev/mrc/dryad_hive/data/input/taxa/dryad_taxa_final_names.txt";
        String pubmedFile = "/Users/cwillis/dev/mrc/dryad_hive/data/input/taxa/pubmed_taxa_final_names.txt";
        String hiveFile = "/Users/cwillis/dev/mrc/dryad_hive/data/input/taxa/hive_taxa_title_desc_final2.txt";
        

        Vocabulary vocabulary = new VocabularyH2();
        vocabulary.setStemmer(new maui.stemmers.PorterStemmer());
        vocabulary.setStopwords("src/main/resources/stopwords_en.txt");
        vocabulary.open("/Users/cwillis/dev/mrc/nlp/memai-mrc/src/main/resources/itis2");  
        
        Vocabulary vocabularyGraph = new VocabularyGraph();
        vocabularyGraph.open("/Users/cwillis/dev/hive/hive-data/itis/itisStore"); 
//        vocabularyGraph.getPrefLabel(id);
        
        List<String> ids = readList(idFile);
        Map<String, List<String>> biosisMap = readMap(biosisFile);
        Map<String, List<String>> pubmedMap = readMap(pubmedFile);
        Map<String, List<String>> dryadMap = readMap(dryadFile);
        Map<String, List<String>> hiveMap = readMap(hiveFile, vocabularyGraph);
        
        for (String id: ids)
        {
            List<String> biosisList = biosisMap.get(id);
            //List<String> pubmedList = pubmedMap.get(id);
            //List<String> dryadList = dryadMap.get(id);
            List<String> hiveList = hiveMap.get(id);
            
            if (biosisList != null && hiveList != null)
            {
                int nm = numMatched(biosisList, hiveList);
                double j = jaccard(biosisList, hiveList);
                double r = rolling(biosisList, hiveList);
                double h = hooper(biosisList, hiveList);
                double c = cosine(biosisList, hiveList);
                double cs = cosineSimilarity(biosisList, hiveList);
                double m = medelyan(vocabularyGraph, biosisList, hiveList);
                
                System.out.println(id + "," + biosisList.size() + "," + hiveList.size() + "," + 
                        nm + "," + j + "," + r + "," + h + "," + c + "," + cs + "," + m);
            }
            else
            {
                if (biosisList == null)
                    biosisList = new ArrayList<String>();
                if (hiveList == null)
                    hiveList = new ArrayList<String>();                
                System.out.println(id + "," + biosisList.size() + "," + hiveList.size() + "," + 
                        0 + "," + 0 + "," + 0 + "," + 0 + "," + 0 + "," + 0 + "," + 0);
            }
            
            
        }
        
    }
    
    public static List<String> readList(String file) throws Exception
    {
        List<String> ids = new ArrayList<String>();
        BufferedReader br = new BufferedReader(new FileReader(file));
        String line;
        while ((line = br.readLine()) != null)
            ids.add(line);
        return ids;
    }
    
    public static Map<String, List<String>> readMap(String file) throws Exception
    {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        BufferedReader br = new BufferedReader(new FileReader(file));
        String line;
        while ((line = br.readLine()) != null)
        {
            String[] fields = line.split("\\|");
            String id = fields[0].trim();
            String value = fields[1].trim();
            
            List<String> values = map.get(id);
            if (values == null)
                values = new ArrayList<String>();
            
            values.add(value);
            map.put(id, values);
        }
        return map;
    }
    
    public static Map<String, List<String>> readMap(String file, Vocabulary vocab) throws Exception
    {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        BufferedReader br = new BufferedReader(new FileReader(file));
        String line;
        while ((line = br.readLine()) != null)
        {
            String[] fields = line.split("\\|");
            String id = fields[0].trim();
            String value = vocab.getPrefLabel(Integer.valueOf(fields[1].trim()));
            if (value != null)
            {
                List<String> values = map.get(id);
                if (values == null)
                    values = new ArrayList<String>();
                
                values.add(value);
                map.put(id, values);

            }
            else
            {
                System.out.println("Failed to find " + fields[1]);
            }
        }
        return map;
    }    
    
    /* What % of terms in list1 are in list2 */
    public static int numMatched(List<String> list1, List<String> list2)
    {
        int matched = 0;
        for (String term1: list1)
        {
            for (String term2: list2)
            {
                if (term1.equals(term2))
                    matched++;
            }
        }
        return matched;
    }
    
    public static double jaccard(List<String> list1, List<String> list2)
    {
        Set<String> set1 = new LinkedHashSet<String>();
        Set<String> set2 = new LinkedHashSet<String>();
        set1.addAll(list1);
        set2.addAll(list2);

        Set<String> allTerms = new TreeSet<String>();
        allTerms.addAll(set1);
        allTerms.addAll(set2);

        // J(A,B) = | A intersect B | / | A union B |

        // Create A's term vector
        Matrix a = getTermVector(set1, allTerms, 1);

        // Create B's term vector
        Matrix b = getTermVector(set2, allTerms, 1);

        double intersection = 0.0D;
        for (int i = 0; i < a.getRowDimension();i++) {
          intersection += Math.min(a.get(i, 0), b.get(i, 0));
        }
        if (intersection > 0.0D) {
          double union = allTerms.size(); //a.norm1() + b.norm1();
          return intersection / union;
        } else {
          return 0.0D;
        }  
    }

    public static double rolling(List<String> list1, List<String> list2)
    {
        // R = 2C / (A+B)
        //   A = size of list1
        //   B = size of list2
        //   C = # terms in common
        double c = 0;
        for (String term: list1) {
                if (list2.contains(term))
                        c++;
        }

        double a = list1.size();
        double b = list2.size();

        double r = (2* c) / (a + b);

        return r;
    }
    
    public static double precision(List<String> relevant, List<String> found)
    {
        //p is the number of correct results divided by the number of all returned results
        double p = 0;
        
        int numRelevantFound = 0;
        for (String f: found)
        {
            if (relevant.contains(f))
                numRelevantFound++;
        }
        
        if (numRelevantFound > 0)
            p = (double)numRelevantFound / (double)found.size();
        
        return p;
    }
    
    public static double recall(List<String> relevant, List<String> found)
    {
        //r is the number of correct results divided by the number of results that should have been returned
        double r = 0;
        
        int numRelevantFound = 0;
        for (String f: found)
        {
            if (relevant.contains(f))
                numRelevantFound++;
        }
        if (numRelevantFound > 0)            
            r = (double)numRelevantFound / (double)relevant.size();
        
        return r;
    }

    public static double hooper(List<String> list1, List<String> list2)
    {
        // H = C / (A + B - C)
        //   A = size of list1
        //   B = size of list2
        //   C = # terms in common
        Set<String> set1 = new LinkedHashSet<String>();
        Set<String> set2 = new LinkedHashSet<String>();
        set1.addAll(list1);
        set2.addAll(list2);
        double c = 0;
        for (String term: set1) {
                if (set2.contains(term))
                        c++;
        }

        double a = set1.size();
        double b = set2.size();

        double h = c / (a + b - c);
        return h;
    }
 
    public static double cosineSimilarity(List<String> list1, List<String> list2)
    {
        Set<String> set1 = new LinkedHashSet<String>();
        Set<String> set2 = new LinkedHashSet<String>();
        set1.addAll(list1);
        set2.addAll(list2);


        Set<String> allTerms = new TreeSet<String>();
        allTerms.addAll(set1);
        allTerms.addAll(set2);

        // Cos =  (A dot B) / |A| * |B|

        Matrix a = getTermVector(set1, allTerms, 1);
        Matrix b = getTermVector(set2, allTerms, 1);

        double dotProduct = a.arrayTimes(b).norm1();
        double euclideanDist = a.normF() * b.normF();

        return (dotProduct / euclideanDist);
    }
    
    public static double cosine(List<String> list1, List<String> list2)
    {
        // Cos = C / sqrt(A*B)
        //   A = size of list1
        //   B = size of list2
        //   C = # terms in common
        double c = 0;
        for (String term: list1) {
                if (list2.contains(term))
                        c++;
        }

        double a = list1.size();
        double b = list2.size();

        double r = 0;
        if (a*b > 0)
            r = c/Math.sqrt(a*b);

        return r;
    }
    
    public static double medelyan (Vocabulary vocabulary, List<String> list1, List<String> list2) throws Exception
    {
        Set<String> set1 = new LinkedHashSet<String>();
        Set<String> set2 = new LinkedHashSet<String>();
        set1.addAll(list1);
        set2.addAll(list2);

        // (A_ (gamma + alpha*R__ + beta*N__) * B_) / ( |A_| * |gamma + alpha*R__ + beta*N__| * |B_|
        //    A = A's vector of terms
        //    B = B's vector of terms
        //    R = Related terms in A/B
        //    N = Broader/narrower terms in A/B

        double gamma = .65;
        double alpha = .20;
        double beta = .15;

        Set<String> allTerms = new TreeSet<String>();
        allTerms.addAll(set1);
        allTerms.addAll(set2);


        // Create the related terms matrix
        Matrix r = getRTMatrix(vocabulary, allTerms, alpha);

        // Create the broader/narrower terms matrix
        Matrix n = getBNMatrix(vocabulary, allTerms, beta);

        // Create the identity matrix
        Matrix i = Matrix.identity(allTerms.size(), allTerms.size());

        //
        Matrix rn = r.plus(n).plus(i);
        //rn.print(5, 2);

        // Create A's term vector
        Matrix a = getTermVector(set1, allTerms, gamma);
        //a.print(5, 2);

        // Create B's term vector
        Matrix b = getTermVector(set2, allTerms, gamma);
        //b.print(5, 2);

        // Create A'
        Matrix aprime = rn.times(a);
        //aprime.print(5,2);
        // Create B'
        Matrix bprime = rn.times(b);
        //bprime.print(5,2);

        // Multiply A' and B'
        Matrix dp = aprime.arrayTimes(bprime);
        //dp.print(5,2);

        // Get the dot product
        double dotProduct = dp.norm1();
        
        // Get the distance
        double euclideanDist = aprime.normF() * bprime.normF();

        return dotProduct/euclideanDist;
    }


    public static Matrix getTermVector(Set<String> localTerms, 
            Set<String> allTerms, double weight)
    {
        int numTerms = allTerms.size();
        Matrix matrix = new Matrix(numTerms, 1);

        int i=0;
        for (String term: allTerms)
        {
            matrix.set(i, 0, localTerms.contains(term) ? weight : 0);
            i++;
        }
        return matrix;
    }

    public static Matrix getRTMatrix(Vocabulary vocabulary, Set<String> terms, double weight) 
        throws Exception
    {
        int numTerms = terms.size();
        Matrix matrix = new Matrix(numTerms, numTerms);

        int i = 0;
        for (String term1: terms) {
            int j = 0;
            for (String term2: terms) {

                boolean isRelated = vocabulary.isRelated(term1, term2);

                matrix.set(i, j, isRelated ? weight:0);
                j++;
            }
            i++;
        }
        return matrix;
    }

    public static Matrix getBNMatrix(Vocabulary vocabulary, Set<String> terms, double weight) 
        throws Exception
    {
        int numTerms = terms.size();
        Matrix matrix = new Matrix(numTerms, numTerms);

        int i = 0;
        for (String term1: terms) {

            int j = 0;
            for (String term2: terms) {

                boolean isBN = vocabulary.isBroaderNarrower(term1, term2);

                matrix.set(i, j, isBN ? weight :0);
                j++;
            }
            i++;
        }
        return matrix;
    }
}
